/**
 * 网络请求工具，对 uni.request、uni.upload 的扩展封装
 * @param {Object} config 当前实例的配置
 * @author 陈老师儿~
 * @mobile 18560000860
 */
export default class PureHttp {
    // 构造函数
    constructor(config) {
        // 默认配置
        const defaultConfig = {
            /**
             * [uni.request() 自带配置](https://uniapp.dcloud.net.cn/api/request/request.html)
             */
            url: "", // 接口地址，可以是绝对路径或者相对路径，如果是相对路径，最终会和 baseURL 合并成一个完整的 URL
            data: {}, // 请求的参数
            header: {}, // 设置请求的 header, header 中不能设置 Referer
            method: "GET", // 请求类型，GET | POST | PUT | DELETE | CONNECT | HEAD | OPTIONS | TRACE | UPLOAD（扩展的）
            timeout: 30000, // 超时时间，单位毫秒
            dataType: "json", // 告诉服务器，我要发送的数据类型，如果设为 json, 会对返回的数据进行一次 JSON.parse, 非 json 不会进行 JSON.parse
            responseType: "text", // 设置响应的数据类型，text | arraybuffer
            sslVerify: true, // 是否验证 ssl 证书？
            withCredentials: false, // 跨域请求时是否携带凭证（cookies）？
            firstIpv4: false, // DNS 解析时是否优先使用 ipv4？
            enableHttp2: false, // 是否开启 http2？
            enableQuic: false, // 是否开启 quic？
            enableCache: false, // 是否开启 cache？
            enableHttpDNS: false, // 是否开启 HttpDNS 服务?
            httpDNSServiceId: "", // HttpDNS 服务商 Id
            enableChunked: false, // 是否开启 transfer-encoding chunked？
            forceCellularNetwork: false, // 是否在 wifi 下使用移动网络发送请求？
            enableCookie: false, // 开启后可在 headers 中编辑 cookie
            cloudCache: false, // 是否开启云加速？
            defer: false, // 控制当前请求是否延时至首屏内容渲染后发送

            /**
             * [uni.upload() 自带配置](https://uniapp.dcloud.net.cn/api/request/network-file.html)
             */
            files: null, // 需要上传的文件列表。使用 files 时，filePath 和 name 不生效
            fileType: "image", // 文件类型，image/video/audio
            file: null, // 要上传的文件对象
            filePath: null, // 要上传文件资源的路径
            name: "file", // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
            formData: {}, // HTTP 请求中其他额外的 form data

            /**
             * 重写 uni.request() 的回调函数
             */
            // HTTP 状态码正常且返回数据的状态码也正常的回调函数
            // 回调参数：接口返回的数据（response.data）和本次请求的最终配置(config)
            // 注意: 此函数被重写了，和 uni.request 或 uni.upload 的 success 不一样了
            success: null,
            // HTTP 状态码异常或返回数据的状态码异常的回调函数
            // 回调参数：errData: 包含错误码和错误文本的对象，如果是接口返回的数据状态码异常，则返回的是接口返回的数据对象; config: 本次请求的最终配置(config)
            // 注意: uni 本身没有此回调函数，该回调函数是为了更好的处理返回数据而扩展增加的
            error: null,
            // 接口请求失败的回调函数
            // 请求未成功
            // 包括: 网络不通 | 请求被强行中断 | 请求超时 | 找不到域名 | ssl握手失败等
            // 回调参数为 errorData: 包含错误码和错误文本的对象和请求配置(config)
            fail: null,
            // 接口调用结束的回调函数(调用成功、失败都会执行)
            // 回调参数为 response 和请求配置(config)
            complete: null,

            /**
             * HiHttp 扩展配置 - 基础配置
             */
            baseURL: "", // 接口基准前缀(一般为接口域名地址)
            requestTokenKey: "Authorization", // 请求时携带的鉴权凭证名称
            responseTokenKey: "Authorization", // 接口返回数据中携带的鉴权凭证名称
            storageTokenKey: "Authorization", // 存储在本地的鉴权凭证名称
            requestRefreshTokenKey: "refresh_token", // 请求时携带的更新鉴权凭证名称
            responseRefreshTokenKey: "refresh_token", // 接口返回数据中携带的更新鉴权凭证名称
            dataStatusKey: "code", // 数据中表示状态码的字段名称
            dataStatusTextKey: "errMsg", // 数据中表示状态文本的字段名称
            dataSuccessStatusCode: 0, // 数据中表示成功状态的状态码
            requestFailStatusCode: -1, // 自定义的表示请求失败的状态码和状态信息，根据 HTTP 规范,408 Request Timeout 表示请求超时
            before: null, // 请求拦截器，参数为此次请求配置(config)，返回true则继续请求，返回false则中断请求
            after: null, // 响应拦截器，回调参数为 response 和请求配置(config)
            // HTTP 状态码和数据状态码处理函数集
            // 所有函数的回调参数都为 response 和请求配置(config)
            codeFunctions: {
                // 例如: 处理 1000 状态码的函数
                // "1000": (response, config) => {},
            },

            /**
             * HiHttp 扩展配置 - 和提示相关的配置
             */
            // Loading - 加载提示
            showLoading: true, // 是否显示 loading?
            loadingTime: 800, // 请求超过此值设置的毫秒数后才会显示，设置为 0 会在请求后立即显示
            loadingText: "", // 提示内容
            loadingMask: true, // 是否显示透明蒙层，防止触摸穿透
            // 自定义显示 loading 的函数，参数为请求配置(config)
            // 注意: 设置了此函数后，还需要对应的配置下方的关闭函数，否则 loading 可能不会被正确关闭，除非你在现实函数中处理了关闭的相关逻辑
            loadingShowFunction: null,
            // 自定义隐藏 loading 的函数，参数为请求配置(config)
            loadingHideFunction: null,

            // Success - 成功提示
            showSuccess: false, // 是否显示提示?
            successText: "", // 提示内容
            successIcon: "none", // 图标，success | error | fail | exception | loading | none
            successImage: "", // 自定义图标的本地路径
            successMask: true, // 是否显示透明蒙层，防止触摸穿透
            successDuration: 2500, // 提示的持续时间，单位毫秒
            successPositon: "", // 提示的显示位置，"top" | "center" | "bottom"，填写有效值后只有 successText 属性生效
            successEndCallback: null, // 提示显示结束后的回调，参数为接口返回的数据和请求配置(config)
            successShowFunction: null, // 自定义显示提示的函数，参数为接口返回的数据和请求配置(config)

            // Error - 错误提示
            showError: false, // 是否显示提示?
            errorText: "", // 提示内容
            errorIcon: "none", // 图标，success | error | fail | exception | loading | none
            errorImage: "", // 自定义图标的本地路径
            errorMask: true, // 是否显示透明蒙层，防止触摸穿透
            errorDuration: 2500, // 提示的持续时间，单位毫秒
            errorPositon: "", // 提示的显示位置，"top" | "center" | "bottom"，填写有效值后只有 errorText 属性生效
            errorEndCallback: null, // 提示显示结束后的回调，参数为错误信息对象和请求配置(config)
            errorShowFunction: null, // 自定义显示提示的函数。参数为错误信息对象和请求配置(config)

            // Fail - 请求失败,包括: 网络不通 | 请求被强行中断 | 请求超时 | 找不到域名 | ssl握手失败等
            showFail: true, // 是否显示提示
            failText: "", // 提示内容
            failIcon: "none", // 图标，success | error | fail | exception | loading | none
            failImage: "", // 自定义图标的本地路径
            failMask: true, // 是否显示透明蒙层，防止触摸穿透
            failDuration: 2500, // 提示的持续时间，单位毫秒
            failPositon: "", // 提示的显示位置，"top" | "center" | "bottom"，填写有效值后只有 failText 属性生效
            failEndCallback: null, // 提示显示结束后的回调，参数为错误信息对象和请求配置(config)
            failShowFunction: null, // 自定义显示提示的函数，参数为错误信息对象和请求配置(config)

            // 状态码
            beforeFalseCode: -20,
            beforeFalseText: "请求被拦截了",
            afterFalseCode: -40,
            afterFalseText: "响应被拦截了",
        };

        // 合并配置
        this.config = this.objectDeepMerge(defaultConfig, config);

        // 任务队列
        this.tasks = {};
    }

    /**
     * 设置本次实例的配置
     * @param {Object} settings 本次实例的配置
     */
    setConfig(settings) {
        this.config = this.objectDeepMerge(this.config, settings);
    }

    /**
     * GET 请求
     * @param {Object} conf 本次请求的配置
     */
    get(conf) {
        conf.method = "GET";
        return this.http(conf);
    }

    /**
     * POST 请求
     * @param {Object} conf 本次请求的配置
     */
    post(conf) {
        conf.method = "POST";
        return this.http(conf);
    }

    /**
     * upload 请求
     * @param {Object} conf 本次请求的配置
     */
    upload(conf) {
        conf.method = "UPLOAD";
        return this.http(conf);
    }

    /**
     * PUT 请求
     * @param {Object} conf 本次请求的配置
     */
    put(conf) {
        conf.method = "PUT";
        return this.http(conf);
    }

    /**
     * DELETE 请求
     * @param {Object} conf 本次请求的配置
     */
    delete(conf) {
        conf.method = "DELETE";
        return this.http(conf);
    }

    /**
     * CONNECT 请求
     * @param {Object} conf 本次请求的配置
     */
    connect(conf) {
        conf.method = "CONNECT";
        return this.http(conf);
    }

    /**
     * HEAD 请求
     * @param {Object} conf 本次请求的配置
     */
    head(conf) {
        conf.method = "HEAD";
        return this.http(conf);
    }

    /**
     * OPTIONS 请求
     * @param {Object} conf 本次请求的配置
     */
    options(conf) {
        conf.method = "OPTIONS";
        return this.http(conf);
    }

    /**
     * TRACE 请求
     * @param {Object} conf 本次请求的配置
     */
    trace(conf) {
        conf.method = "TRACE";
        return this.http(conf);
    }

    /**
     * 请求
     * @param {Object} conf 本次请求的配置
     */
    http(conf) {
        // 合并配置，得到本次请求的最终配置
        let _conf = this.objectDeepMerge(this.config, conf);

        // 本次请求的 Task ID
        const taskId = Symbol("pure-request-task");

        // 本次请求的Promise
        let promise;

        // 请求被拦截
        if (this.isFunction(_conf?.before) && !_conf.before(_conf)) {
            promise = Promise.reject({
                [_conf.dataStatusKey]: _conf.beforeFalseCode,
                [_conf.dataStatusTextKey]: _conf.beforeFalseText,
            });
        }

        // 请求未被拦截
        else {
            // 判断是否需要显示本次请求的 loading 的逻辑
            this.isLoadingLogic(_conf);

            // 完整的请求地址
            _conf.url = this.urlCombine(_conf.baseURL, _conf.url);

            // 设置鉴权 TOKEN
            this.setToken(_conf);

            // 创建一个请求 Promise
            promise = new Promise((resolve, reject) => {
                // 请求方法。request | upload
                let uniMethod = _conf.method.toUpperCase() === "UPLOAD" ? uni.uploadFile : uni.request;

                // 通过 requestTask，可中断请求任务
                this.tasks[taskId] = uniMethod({
                    ..._conf,

                    // response.data: 开发者服务器返回的数据
                    // response.statusCode: 开发者服务器返回的 HTTP 状态码
                    // response.header: 开发者服务器返回的 HTTP Response Header
                    // response.cookies[Array.<string>]: 开发者服务器返回的 cookies，格式为字符串数组
                    success: (resp) => {
                        // 更新鉴权 TOKEN
                        this.updateToken(resp, _conf);

                        // 如果返回数据是字符串，尝试处理一下
                        if (this.isString(resp.data) && _conf.dataType === "json") {
                            try {
                                resp.data = JSON.parse(resp.data);
                            } catch (err) {}
                        }

                        // 响应拦截
                        if (this.isFunction(_conf.after)) {
                            const afterResult = _conf.after(resp, _conf);
                            if (!afterResult) {
                                reject({
                                    [_conf.dataStatusKey]: _conf.afterFalseCode,
                                    [_conf.dataStatusTextKey]: _conf.afterFalseText,
                                });
                                return;
                            }
                        }

                        // 处理 HTTP 和返回数据的各种状态码
                        const codeFunctionsResult = this.codeFunctions(resp, _conf);
                        if (codeFunctionsResult !== true) {
                            reject(codeFunctionsResult);
                            return;
                        }

                        // 处理 HTTP 和返回数据的各种状态码
                        if (resp.statusCode === 200) {
                            // HTTP 状态码正常
                            // 数据状态码正常
                            if (resp.data?.[_conf?.dataStatusKey] === _conf?.dataSuccessStatusCode) {
                                // 判断是否需要显示本次请求的 suceess 提示的逻辑
                                this.isShowSuccessLogic(resp.data, _conf);

                                // 调用重写的 success 函数
                                this.isFunction(_conf.success) && _conf.success(resp.data, _conf);

                                // 结束 Promise
                                resolve(resp.data);
                            }

                            // 数据状态码异常
                            else {
                                // 判断是否需要显示本次请求的 error 提示的逻辑
                                this.isShowErrorLogic(resp.data, _conf);

                                // 调用重写 error 函数
                                this.isFunction(_conf.error) && _conf.error(resp.data, _conf);

                                // 结束 Promise
                                reject(resp.data);
                            }
                        }

                        // HTTP 状态码异常
                        else {
                            const errObj = {
                                [_conf.dataStatusKey]: resp.statusCode,
                                [_conf.dataStatusTextKey]: resp.statusText,
                            };

                            // 判断是否需要显示本次请求的 error 提示的逻辑
                            this.isShowErrorLogic(errObj, _conf);

                            // 调用重写的 error 函数
                            this.isFunction(_conf.error) && _conf.error(errObj, _conf);

                            // 结束 Promise
                            reject(errObj);
                        }
                    },

                    // 请求失败,包括: 网络不通 | 请求被强行中断 | 请求超时 | 找不到域名 | ssl握手失败等
                    // 1. 通过 requestTask.abort() 中止的请求, error = {errMsg: 'request:fail abort'}
                    // 2. 网络离线时, error = {errMsg: 'request:fail'}
                    fail: (error) => {
                        // 响应拦截
                        if (this.isFunction(_conf.after)) {
                            const afterResult = _conf.after(resp, _conf);
                            if (!afterResult) {
                                reject({
                                    [_conf.dataStatusKey]: _conf.afterFalseCode,
                                    [_conf.dataStatusTextKey]: _conf.afterFalseText,
                                });
                                return;
                            }
                        }

                        // 处理 HTTP 和返回数据的各种状态码
                        const codeFunctionsResult = this.codeFunctions(resp, _conf);
                        if (codeFunctionsResult !== true) {
                            reject(codeFunctionsResult);
                            return;
                        }

                        // 错误信息
                        const errObj = {
                            [_conf.dataStatusKey]: _conf.requestFailStatusCode,
                            [_conf.dataStatusTextKey]: error?.errMsg || _conf.failText,
                        };

                        // 判断是否需要显示本次请求的 fail 提示的逻辑
                        this.isShowFailLogic(errObj, _conf);

                        // 调用重写的 fail 函数
                        this.isFunction(_conf.fail) && _conf.fail(errObj, _conf);

                        // 结束 Promise
                        reject(errObj);
                    },

                    complete: (resp) => {
                        // 判断是否需要关闭本次请求的 loading 的逻辑
                        this.isCloseLodingLogic(_conf);

                        // 调用重写的 complete 函数
                        this.isFunction(_conf.complete) && _conf.complete(resp, _conf);

                        // 删除 task
                        delete this.tasks[taskId];
                    },
                });
            });
        }

        // 如果设置了 success、error、fail、complete 函数，则返回任务id
        if (this.isFunction(_conf?.success) || this.isFunction(_conf?.error) || this.isFunction(_conf?.fail) || this.isFunction(_conf?.complete)) return this.tasks[taskId];

        // 否则返回 Promise
        return promise;
    }

    /**
     * 中断并清除所有请求任务（tasks）
     */
    aborts() {
        const taskIds = Object.getOwnPropertySymbols(this.tasks);
        taskIds.forEach((taskId) => {
            this.tasks[taskId].abort();
            delete this.tasks[taskId];
        });
        this.tasks = {};
    }

    /**
     * 判断是否需要显示本次请求的 loading 的逻辑
     * @param {Object} config 本次请求最终的配置
     */
    isLoadingLogic(config) {
        // 显示 loading
        if (config?.showLoading) {
            // 显示自定义 loading
            if (this.isFunction(config?.loadingShowFunction)) {
                config.loadingShowFunction(config);
                return;
            }

            // 显示默认 loading
            config.loadingTimer = setTimeout(() => {
                config.loadingInstance = uni.showLoading({
                    title: config.loadingText,
                    mask: config.loadingMask,
                });
            }, config?.loadingTime || 0);
        }
    }

    /**
     * 判断是否需要关闭本次请求的 loading 的逻辑
     * @param {Object} config 本次请求最终的配置
     */
    isCloseLodingLogic(config) {
        // 是否需要关闭 loading?
        if (config.showLoading) {
            // 是关闭自定义 loading 还是默认 loading
            if (this.isFunction(config.loadingHideFunction)) {
                config.loadingHideFunction(config);
            }

            // 清除定时器
            if (config.loadingTimer) {
                clearTimeout(config.loadingTimer);
                config.loadingTimer = null;
            }

            // 清除 loading 实例
            if (config.loadingInstance) {
                uni.hideLoading();
                config.loadingInstance = null;
            }
        }
    }

    /**
     * 设置鉴权 TOKEN
     * @param {Object} config 本次请求最终的配置
     */
    setToken(config) {
        if (config.requestTokenKey) {
            config.header[config.requestTokenKey] = uni.getStorageSync(config.storageTokenKey);
        }
    }

    /**
     * 更新鉴权 TOKEN
     * @param {Object} response 本次请求的响应数据
     * @param {Object} config 本次请求最终的配置
     */
    updateToken(response, config) {
        if (response.header?.[config?.responseTokenKey]) {
            uni.setStorageSync(config.storageTokenKey, response.header[config.responseTokenKey]);
        }
    }

    /**
     * 处理 HTTP 和数据的各种状态码
     * @param {Object} response 本次请求的响应数据
     * @param {Object} config 本次请求最终的配置
     */
    codeFunctions(response, config) {
        const httpStatusCode = response.statusCode;
        const dataStatusCode = response.data?.[config?.dataStatusKey];
        const codeFunctions = config.codeFunctions;
        if (this.isFunction(codeFunctions?.[httpStatusCode])) {
            return codeFunctions[httpStatusCode](response, config);
        }
        if (this.isFunction(codeFunctions?.[dataStatusCode])) {
            return codeFunctions[dataStatusCode](response, config);
        }
        return true;
    }

    /**
     * 判断是否需要显示本次请求的 suceess 提示的逻辑
     * @param {Object} data 本次请求的响应数据
     * @param {Object} config 本次请求最终的配置
     */
    isShowSuccessLogic(data, config) {
        // 显示 success 提示
        if (config.showSuccess) {
            // 显示自定义 success 提示
            if (this.isFunction(config.successShowFunction)) {
                config.successShowFunction(data, config);
            }

            // 显示默认 success 提示
            else {
                uni.showToast({
                    title: config?.successText || data?.[config?.dataStatusTextKey],
                    icon: config.successIcon,
                    image: config.successImage,
                    mask: config.successMask,
                    duration: config.successDuration,
                    position: config.successPositon,
                });
            }
        }

        // 配置了 success 提示结束后的回调函数
        if (this.isFunction(config.successEndCallback)) {
            setTimeout(() => {
                config.successEndCallback(data, config);
            }, config.successDuration);
        }
    }

    /**
     * 判断是否需要显示本次请求的 error 提示的逻辑
     * @param {Object} data 错误信息
     * @param {Object} config 本次请求最终的配置
     */
    isShowErrorLogic(data, config) {
        // 需要显示 error 提示
        if (config.showError) {
            // 显示自定义 error 提示
            if (this.isFunction(config.errorShowFunction)) {
                config.errorShowFunction(data, config);
            }

            // 显示默认 error 提示
            else {
                uni.showToast({
                    title: config?.errorText || data?.[config?.dataStatusTextKey],
                    icon: config.errorIcon,
                    image: config.errorImage,
                    mask: config.errorMask,
                    duration: config.errorDuration,
                    position: config.errorPositon,
                });
            }
        }

        // 配置了 error 提示结束后的回调函数
        if (this.isFunction(config.errorEndCallback)) {
            setTimeout(() => {
                config.errorEndCallback(data, config);
            }, config.errorDuration);
        }
    }

    /**
     * 判断是否需要显示本次请求的 fail 提示的逻辑
     * @param {Object} data 错误信息
     * @param {Object} config 本次请求最终的配置
     */
    isShowFailLogic(data, config) {
        // 需要显示 fail 提示
        if (config.showFail) {
            // 显示自定义 fail 提示
            if (this.isFunction(config.failShowFunction)) {
                config.failShowFunction(data, config);
            }

            // 显示默认 fail 提示
            else {
                uni.showToast({
                    title: config?.failText || data?.[config?.dataStatusTextKey],
                    icon: config.failIcon,
                    image: config.failImage,
                    mask: config.failMask,
                    duration: config.failDuration,
                    position: config.failPositon,
                });
            }
        }

        // 配置了 fail 提示结束后的回调函数
        if (this.isFunction(config.failEndCallback)) {
            setTimeout(() => {
                config.failEndCallback(data, config);
            }, config.failDuration);
        }
    }

    /**
     * 验证数据是否是 String 类型数据
     * @param {Any} variable 要验证的数据
     * @returns {Boolean} true: 是 String 类型数据；false: 不是 String 类型数据；
     */
    isString(variable) {
        return typeof variable === "string";
    }

    /**
     * 验证数据是否是 Array 类型数据
     * @param {Any} variable 要验证的数据
     * @returns {Boolean} true: 是 Array 类型数据；false: 不是 Array 类型数据；
     */
    isArray(variable) {
        return Array.isArray(variable);
    }

    /**
     * 验证数据是否是 Array 类型数据
     * Tips: 不包括数组、函数、null等
     * @param {Object} variable 要验证的数据
     * @returns {Boolean} true: 是 Object 类型数据；false: 不是 Object 类型数据；
     */
    isObject(variable) {
        // 排除 null 和非对象类型
        if (variable === null || typeof variable !== "object") return false;

        // 使用 Object.prototype.toString.call 来获取变量的真实类型
        // 注意这里需要排除函数类型，因为函数在JavaScript中也是对象，但通常我们不希望将它们视为普通对象
        return Object.prototype.toString.call(variable) === "[object Object]";
    }

    /**
     * 验证数据是否是 Function 类型数据
     * @param {Any} variable 需要验证的数据
     * @returns {Boolean} true: 是Function 类型数据；false: 不是Function 类型数据；
     */
    isFunction(variable) {
        return Object.prototype.toString.call(variable) === "[object Function]";
    }

    /**
     * 校验 URL 是否是绝对 URL
     * Tips: 如果 URL 以 “<scheme>：//” 或 “//”（协议相对URL）开头，则认为它是绝对的，RFC 3986 将方案名称定义为以字母开头的字符序列，然后是字母，数字，加号，句点或连字符的任意组合
     * @param {String} url 需要校验的 URL
     * @returns {Boolean} true: 是绝对 URL；false: 不是绝对 URL
     *
     */
    isAbsoluteURL(url = "") {
        return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
    }

    /**
     * 深度合并两个对象
     * @param {Object} object1 对象一
     * @param {Object} object2 对象二
     * @returns {Object} 合并后的新对象数据，不影响原对象
     */
    objectDeepMerge(object1, object2) {
        let merged = Object.assign({}, object1);
        for (let key in object2) {
            if (object2.hasOwnProperty(key)) {
                if (this.isObject(object2[key]) && !this.isArray(object2[key])) {
                    if (this.isObject(merged[key]) && !this.isArray(merged[key])) {
                        merged[key] = this.objectDeepMerge(merged[key], object2[key]);
                    } else {
                        merged[key] = Object.assign({}, object2[key]);
                    }
                } else {
                    merged[key] = object2[key];
                }
            }
        }
        return merged;
    }

    /**
     * 合并基准 URL 和相对 URL 成一个完整的 URL
     * @param {String} baseURL 基准 URL
     * @param {String} relativeURL 相对 URL
     * @returns {String} 返回合并后的 URL
     */
    urlCombine(baseURL = "", relativeURL = "") {
        if (this.isAbsoluteURL(relativeURL)) return relativeURL;
        if (relativeURL && this.isString(relativeURL) && this.isString(baseURL)) {
            return baseURL.replace(/\/+$/, "") + "/" + relativeURL.replace(/^\/+/, "");
        }
        return baseURL;
    }
}
